package com.shadow.demo.dictionary.common;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Timestamp;
import java.util.*;

/**
 * 反射辅助工具类
 *
 * @author penggch
 */
public class ReflectUtil {

    public static final String IdField = "id";

    public static final String NameField = "name";

    public static final String ParentIdField = "parentId";

    public static final Map<Class<?>, Object> ClassIdGetterMap = new HashMap<Class<?>, Object>();
    private static final Logger log = LoggerFactory.getLogger(ReflectUtil.class);

    public static <T> List<T> listIds(Class<T> cls, Object obj) {
        return listFieldValue(cls, IdField, obj, true);
    }

    public static <T> List<T> listNames(Class<T> cls, Object obj) {
        return listFieldValue(cls, NameField, obj, true);
    }

    public static <T> List<T> listParentIds(Class<T> cls, Object obj) {
        return listFieldValue(cls, ParentIdField, obj, true);
    }

    public static <T> List<T> listFieldValue(Class<T> fieldType, String fieldname, Object obj, boolean removeEmpty) {
        List<T> list = new ArrayList<T>();
        if (obj != null && obj.getClass().isArray()) {
            Map<Class<?>, Object> getterMap = new HashMap<Class<?>, Object>();
            int length = Array.getLength(obj);
            for (int i = 0; i < length; i++) {
                Object item = Array.get(obj, i);
                Object value = getFieldValue(getterMap, fieldname, item);
                if (removeEmpty && isEmpty(value)) {
                    continue;
                }
                list.add(ParseUtil.parseValue(fieldType, value));
            }

        } else if (obj instanceof Collection) {
            Map<Class<?>, Object> getterMap = new HashMap<Class<?>, Object>();
            for (Object item : (Collection<?>) obj) {
                Object value = getFieldValue(getterMap, fieldname, item);
                if (removeEmpty && isEmpty(value)) {
                    continue;
                }
                list.add(ParseUtil.parseValue(fieldType, value));
            }
        } else if (obj != null) {
            T value = getFieldValue(fieldType, fieldname, obj);
            list.add(value);
        }
        return list;
    }

    public static <T> T getFieldValue(Class<T> fieldType, String fieldname, Object obj) {
        if (obj != null) {
            Method method = getGetterMethod(obj.getClass(), fieldname);
            if (method != null) {
                try {
                    Object value = method.invoke(obj);
                    return ParseUtil.parseValue(fieldType, value);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return null;
    }

    private static boolean isEmpty(Object value) {
        if(value==null|| (value instanceof String && ((String) value).length() == 0)) {
            return true;
        }
        return false;
    }

    private static Object getFieldValue(Map<Class<?>, Object> getterMap, String fieldname, Object obj) {
        if (obj == null) {
            return null;
        }
        Object value = null;
        Object getter = null;
        Class<?> objClass = obj.getClass();
        if (getterMap != null && getterMap.containsKey(objClass)) {
            getter = getterMap.get(objClass);
        }

        try {
            if (getter == null) {
                getter = getGetterMethod(objClass, fieldname);
                if (getter != null && getterMap != null) {
                    getterMap.put(objClass, getter);
                }
            }
            if (getter == null) {
                getter = getGetterField(objClass, fieldname);
                if (getter != null && getterMap != null) {
                    getterMap.put(objClass, getter);
                }
            }
            if (getter instanceof Method) {
                value = ((Method) getter).invoke(obj);
            } else if (getter instanceof Field) {
                value = ((Field) getter).get(obj);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return value;
    }

    /**
     * 获取用来取值的方法
     *
     * @param cls  类型
     * @param name 方法对应的属性名
     * @return
     */
    private static Method getGetterMethod(Class<?> cls, String name) {
        Method[] methods = cls.getMethods();
        if (methods.length > 0) {
            for (Method method : methods) {
                if (method.getReturnType() == null ||
                        (method.getParameters() != null && method.getParameters().length > 0)) {
                    continue;
                }
                if (method.getName().equals("get" + name) || method.getName().equals("is" + name)) {
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 获取用来取值的属性字段
     *
     * @param cls  类型
     * @param name 字段名称
     * @return
     */
    private static Field getGetterField(Class<?> cls, String name) {
        Field[] fields = cls.getFields();
        if (fields.length > 0) {
            for (Field field : fields) {
                if (field.getName().equals(name)) {
                    return field;
                }
            }
        }
        return null;
    }

    public static Object getValue(Object tableObj, String fieldName) throws Exception {
        return getValue(tableObj, fieldName, null);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static Object getValue(Object tableObj, String fieldName, Object nullValue) throws Exception {
        if (tableObj instanceof Map) {
            Map obj = (Map) tableObj;
            Iterator iterator = obj.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next().toString();
                if (key.equalsIgnoreCase(fieldName)) {
                    return obj.get(key);
                }
            }
            return nullValue;
        }

        Class hq = tableObj.getClass();
        Object value = null;
        String stringLeter = fieldName.substring(0, 1).toUpperCase();
        String getName = "get" + stringLeter + fieldName.substring(1);
        Method getMethod = cn.hutool.core.util.ReflectUtil.getMethodIgnoreCase(hq, getName);
//		try {
//			getMethod = hq.getMethod(getName, new Class[] {});
//		} catch (Exception ex) {
//			LogUtil.error(ex);
//		}
//		if (getMethod == null) {
//			getName = "get" + stringLeter + fieldName.substring(1).toLowerCase();
//			getMethod = hq.getMethod(getName, new Class[] {});
//		}
        if (getMethod != null) {
            value = getMethod.invoke(tableObj, new Object[]{});
            return value;
        } else {
            Field[] fields = hq.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                String fieldNames = fields[i].getName();
                if (fieldNames.equalsIgnoreCase(fieldName)) {
                    value = fields[i].get(tableObj);
                    return value;
                }
            }
        }
        log.warn("没有找到对象：{}属性{}", tableObj.getClass(), fieldName);
        return nullValue;
    }

    /**
     * 设置属性值
     *
     * @param obj
     * @param javaType
     * @param propertyName
     * @param value
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void setValue(Object obj, String javaType, String propertyName, Object value) {
        try {

            Object realValue = value;
            Class realType = Class.forName(javaType);
            String realName = propertyName;
            // 值转换
            if (value != null) {
                if (value.toString().length() > 0) {
                    realValue = convert(realType, value);
                } else {
                    if (!realType.equals(String.class)) {
                        realValue = null;
                    }
                }
            }

            // 设置值
            if (obj instanceof Map) {
                ((Map) obj).put(propertyName, realValue);
            } else {
                String firstLetter = realName.substring(0, 1).toUpperCase();
                String lastLetter = realName.substring(1);
                String setName = "set" + firstLetter + lastLetter;
                Method setMethod = obj.getClass().getMethod(setName, realType);
                setMethod.invoke(obj, realValue);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public static Object newInstance(String className, Object... args) {
        try {
            Class<?> cls = Class.forName(className);
            return newInstance(cls, args);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Object newInstance(Class<?> cls, Object... args) {
        Object inst = null;
        try {
            if (args == null || args.length == 0) {
                inst = cls.newInstance();//cls.getDeclaredConstructor().newInstance();
            } else {
                Constructor<?> constructor = getConstructor(cls, args);
                inst = constructor.newInstance(args);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return inst;
    }

    private static Constructor<?> getConstructor(Class<?> cls, Object... args)
            throws NoSuchMethodException, SecurityException {
        Constructor<?> result = null;
        if (args == null || args.length == 0) {
            result = cls.getDeclaredConstructor();
        } else {
            Constructor<?>[] constructors = cls.getDeclaredConstructors();
            int priority = -1;
            for (Constructor<?> constructor : constructors) {
                Class<?>[] paramTypes = constructor.getParameterTypes();
                if (paramTypes == null || paramTypes.length != args.length) {
                    continue;
                }
                int cpriority = -1;
                for (int i = 0; i < args.length; i++) {
                    Class<?> paramType = paramTypes[i];
                    Object arg = args[i];
                    if (arg == null) {
                        cpriority += 1;
                    } else if (paramType.getName().equals(arg.getClass().getName())) {
                        cpriority += 100;
                    } else if (paramType.isAssignableFrom(arg.getClass())) {
                        cpriority += 10;
                    } else {
                        cpriority = -1;
                        break;
                    }
                }
                if (cpriority > priority) {
                    result = constructor;
                }
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    public static <T> T newInstance(Class<T> cls, String className, Object... args) {
        T t = null;
        try {
            if (className != null && className.length() > 0) {
                Class<?> nclass = Class.forName(className);
                if (cls.getName().equals(className) || cls.isAssignableFrom(nclass)) {
                    t = (T) newInstance(nclass, args);
                } else {
                    throw new RuntimeException("class not matched");
                }
            } else {
                t = (T) newInstance(cls, args);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return t;
    }

    public static boolean isSetMethod(Method method) {
        if (method != null && method.getName().length() > 3 && method.getName().startsWith("set")
                && method.getParameterTypes() != null && method.getParameterTypes().length == 1) {
            return true;
        }
        return false;
    }

    public static boolean isGetMethod(Method method) {
        if (method == null) {
            return false;
        }
        if (method.getParameterTypes() != null && method.getParameterTypes().length > 0) {
            return false;
        }
        Class<?> returnType = method.getReturnType();
        if (returnType == null) {
            return false;
        }
        String name = method.getName();
        if ((name.startsWith("is") && name.length() > 2)) {
            if (returnType.getName().equals(boolean.class.getName())
                    || returnType.getName().equals(Boolean.class.getName())) {
                return true;
            }
        } else if (name.startsWith("get") && name.length() > 3) {
            return true;
        }
        return false;
    }

    @SuppressWarnings("rawtypes")
    public static Object getIdValue(Object object) {
        Object idValue = null;
        if (object == null) {
            return idValue;
        }

        if (object instanceof Map) {
            Map map = (Map) object;
            if (map.containsKey("id")) {
                idValue = map.get("id");
            } else if (map.containsKey("ID")) {
                idValue = map.get("ID");
            } else if (map.containsKey("Id")) {
                idValue = map.get("Id");
            } else if (map.containsKey("iD")) {
                idValue = map.get("iD");
            }
            return idValue;
        }

        Class<?> cls = object.getClass();
        Field idField = null;
        Method idMethod = null;

        if (ClassIdGetterMap.containsKey(cls)) {
            Object getter = ClassIdGetterMap.get(cls);
            if (getter instanceof Field) {
                idField = (Field) getter;
                try {
                    makeAccessible(idField);
                    idValue = idField.get(object);
                } catch (Exception e) {
                    log.warn("", e);
                }
            } else if (getter instanceof Method) {
                idMethod = (Method) getter;
                try {
                    makeAccessible(idMethod);
                    idValue = idMethod.invoke(object);
                } catch (Exception e) {
                    log.error("", e);
                }
            }
            return idValue;
        }

        if (idField == null && idMethod == null) {
            idMethod = getIdMethod(cls);
        }

        if (idField == null && idMethod == null) {
            idField = getIdField(cls);
        }

        try {
            if (idField != null) {
                makeAccessible(idField);
                idValue = idField.get(object);
                ClassIdGetterMap.put(cls, idField);
            } else if (idMethod != null) {
                makeAccessible(idMethod);
                idValue = idMethod.invoke(object);
                ClassIdGetterMap.put(cls, idMethod);
            }
        } catch (Exception e) {
            log.error("", e);
        }

        return idValue;
    }

    private static Method getIdMethodByAnnotation(Class<?> cls) {
        // 基于注解获取Id方法
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
//            Id id = method.getAnnotation(Id.class);
//            if (id != null
//                    && !Modifier.isStatic(method.getModifiers())
//                    && (method.getParameterTypes() == null || method.getParameterAnnotations().length == 0)) {
//                return method;
//            }
        }
        return null;
    }

    private static Field getIdFieldByAnnotation(Class<?> cls) {
        // 基于注解获取Id属性
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
//            Id id = field.getAnnotation(Id.class);
//            if (id != null
//                    && !Modifier.isStatic(field.getModifiers())) {
//                return field;
//            }
        }
        return null;
    }

    private static Method getIdMethod(Class<?> cls) {
        // 基于名称获取Id方法
        Method[] methods = cls.getDeclaredMethods();
        String methodName = "getId";
        for (Method method : methods) {
            if (method.getName().equalsIgnoreCase(methodName) && !Modifier.isStatic(method.getModifiers())) {
                return method;
            }
        }
        return getIdMethodByAnnotation(cls);
    }

    private static Field getIdField(Class<?> cls) {
        // 基于名称获取Id属性
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equalsIgnoreCase("id")
                    && !Modifier.isStatic(field.getModifiers())) {
                return field;
            }
        }
        return getIdFieldByAnnotation(cls);
    }


    private static void callStaticMethod(Class<?> cls, String methodName) {
        Method[] methods = cls.getDeclaredMethods();
        if (methods != null) {
            for (Method method : methods) {
                if (!method.getName().equals(methodName)) {
                    continue;
                }
                if (!Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                if (method.getParameters() != null && method.getParameters().length > 0) {
                    continue;
                }
                try {
                    method.invoke(null);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private static boolean tryCallStaticMethod(Class<?> cls, String methodName) {
        try {
            callStaticMethod(cls, methodName);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 清理/清洗，旨在删除有问题的内容
     *
     * @param cls
     */
    public static void clean(Class<?> cls) {
        callStaticMethod(cls, "clean");
    }

    /**
     * 尝试清理
     *
     * @param cls
     * @return
     */
    public static boolean tryClean(Class<?> cls) {
        return tryCallStaticMethod(cls, "clean");
    }

    /**
     * 清空，删除包含的所有内容
     *
     * @param cls
     */
    public static void clear(Class<?> cls) {
        callStaticMethod(cls, "clear");
    }

    /**
     * 尝试清空
     *
     * @param cls
     * @return
     */
    public static boolean tryClear(Class<?> cls) {
        return tryCallStaticMethod(cls, "clear");
    }

    /**
     * 获取求等于的方法
     *
     * @param cls
     * @return
     */
    public static Method getEqualsMethod(Class<?> cls) {
        Method[] methods = cls.getDeclaredMethods();
        if (methods != null) {
            for (Method method : methods) {
                if (!method.getName().equals("equals")) {
                    continue;
                }
                if (Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                if (!method.isAccessible()) {
                    continue;
                }
                if (method.getParameters() == null || method.getParameters().length != 1) {
                    continue;
                }
                if (method.getReturnType() == null
                        || !method.getReturnType().getName().equals(boolean.class.getName())) {
                    continue;
                }
                return method;
            }
        }
        return null;
    }

    public static boolean isValidClass(String className) {
        try {
            if (className == null || className.length() == 0) {
                return false;
            }
            if (Class.forName(className) != null) {
                return true;
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getValueByReturnType(Class<T> returnType, Object valueObject) {
        List<T> values = new ArrayList<T>();
        Field[] fields = cn.hutool.core.util.ReflectUtil.getFields(valueObject.getClass());
        for (Field field : fields) {
            if (!returnType.isAssignableFrom(field.getType())) {
                continue;
            }

            try {
                makeAccessible(field);
                values.add((T) field.get(valueObject));
            } catch (Exception e) {
                log.error("", e);
            }
        }
        Method[] methods = cn.hutool.core.util.ReflectUtil.getMethods(valueObject.getClass());
        for (Method method : methods) {
            if (!returnType.isAssignableFrom(method.getReturnType())
                    || (method.getParameters() != null && method.getParameters().length > 0)) {
                continue;
            }
            try {
                makeAccessible(method);
                values.add((T) method.invoke(valueObject));
            } catch (Exception e) {
                log.error("", e);
            }
        }
        T result = values.stream().filter(item -> item != null).findFirst().get();
        return result;
    }

    /***
     *
     * @param className
     *          类名
     * @param fieldMap
     *         属性值
     * @param methodName
     *          方法名
     * @param objs
     *          参数
     * @return
     */
    public static Object reflectInvoke(String className, Map<String, Object> fieldMap, String methodName, Object... objs) {
        Class<?> clazz = null;
        Object object = null;
        Object rspStr = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("", e);
            return rspStr;
        }
        object = BeanUtil.getBeanByType(clazz);

        //给私有属性赋值
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            makeAccessible(fields[i]);
            if (fieldMap == null) {
                break;
            }
            for (Map.Entry<String, Object> entry : fieldMap.entrySet()) {
                if (fields[i].getName().equals(entry.getKey())) {
                    try {
                        fields[i].set(object, entry.getValue());
                    } catch (IllegalAccessException e) {
                        log.error("", e);
                    }
                }
            }
        }
        //获取到对应得方法
        Method[] allMethods = clazz.getDeclaredMethods();
        Method method = null;
        for (Method methods : allMethods) {
            if (methods.getName().equals(methodName)) {
                method = methods;
            }
        }
        if (method == null) {
            throw new RuntimeException("Not found  method");
        }

        try {
            rspStr = method.invoke(object, objs);
        } catch (Exception e) {
            log.error("", e);
        }

        return rspStr;
    }

    public static void makeAccessible(Field field) {
        ReflectionUtils.makeAccessible(field);
    }

    public static void makeAccessible(Method method) {
        ReflectionUtils.makeAccessible(method);
    }

    public static Object convert(Class<?> realType, Object value) {
        if (value == null) {
            return null;
        }
        if (value.toString().length() > 0) {
            if (realType.equals(value.getClass())) {
                return value;
            } else if (realType.equals(BigDecimal.class)) {
                return convert2BigDecimal(value);
            } else if (realType.equals(Byte.class) || realType.equals(byte.class)) {
                return convert2Byte(value);
            } else if (realType.equals(Short.class) || realType.equals(short.class)) {
                return convert2Short(value);
            } else if (realType.equals(Integer.class) || realType.equals(int.class)) {
                return convert2Integer(value);
            } else if (realType.equals(Long.class) || realType.equals(long.class)) {
                return conert2Long(value);
            } else if (realType.equals(Double.class) || realType.equals(double.class)) {
                return convert2Double(value);
            } else if (realType.equals(Boolean.class) || realType.equals(boolean.class)) {
                return convert2Boolean(value);
            } else if (realType.equals(Date.class) || realType.equals(Timestamp.class)) {
                return convert2Date(value);
            } else if (realType.equals(String.class)) {
                return convert2String(value);
            }
        } else {
            if (!realType.equals(String.class))
                return null;
        }
        return null;
    }

    private static Object convert2String(Object value) {
        Object realValue = null;
        if (value instanceof Boolean) {
            realValue = Boolean.TRUE.equals(value) ? "1" : "0";
        } else if (value instanceof Number) {
            realValue = value.toString();
        } else if (value instanceof Date) {
            realValue = DateUtil.formatDate((Date) value);
        } else if (value instanceof Blob) {
            realValue = ParseUtil.blob2String((Blob) value);
        }
        return realValue;
    }

    private static Object convert2Date(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Date) {
            realValue = value;
        } else if (value instanceof Number) {
            Date date = new Date();
            date.setTime(((Number) value).longValue());
            realValue = date;
        } else {
            realValue = DateUtil.parse(value.toString());
        }
        return realValue;
    }

    private static Object convert2Boolean(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).intValue() == 1;
        } else if ("1".equals(value)
                || Integer.valueOf(1).equals(value)
                || "true".equalsIgnoreCase(value.toString())
                || "Y".equalsIgnoreCase(value.toString())) {
            realValue = true;
        } else if ("0".equals(value)
                || Integer.valueOf(0).equals(value)
                || value.toString().equalsIgnoreCase("false")) {
            realValue = false;
        } else {
            realValue = Boolean.parseBoolean(value.toString());
        }
        return realValue;
    }

    private static Object convert2Double(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).doubleValue();
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1d;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0d;
        } else {
            realValue = Double.parseDouble(value.toString());
        }
        return realValue;
    }

    private static Object conert2Long(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).longValue();
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1l;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0l;
        } else {
            realValue = Long.parseLong(value.toString());
        }
        return realValue;
    }

    private static Object convert2Integer(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).intValue();
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0;
        } else {
            realValue = Integer.parseInt(value.toString());
        }
        return realValue;
    }

    private static Object convert2Short(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).shortValue();
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0;
        } else {
            realValue = Short.parseShort(value.toString());
        }
        return realValue;
    }

    private static Object convert2Byte(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof Number) {
            realValue = ((Number) value).byteValue();
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0;
        } else {
            realValue = Byte.parseByte(value.toString());
        }
        return realValue;
    }

    private static Object convert2BigDecimal(Object value) {
        Object realValue;
        if (value == null) {
            return null;
        } else if (value instanceof BigDecimal) {
            realValue = value;
        } else if (value instanceof Number) {
            realValue = BigDecimal.valueOf(((Number) value).doubleValue());
        } else if (value.toString().equalsIgnoreCase("true")) {
            realValue = 1;
        } else if (value.toString().equalsIgnoreCase("false")) {
            realValue = 0;
        } else {
            realValue = new BigDecimal(value.toString());
        }
        return realValue;
    }
}
